home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Wayzata's Best of Shareware PC/Windows 2
/
Wayzata's Best of Shareware 2.0 (Windows) (Wayzata Technology)(7112)(1994).bin
/
pc
/
dos
/
programg
/
pbwiz15
/
pbwiz.doc
< prev
next >
Wrap
Text File
|
1992-10-04
|
68KB
|
1,788 lines
The PowerBASIC Wizard's Library
=-----------------------------=
Version 1.5
PBWIZ Copyright (c) 1991-1992 Thomas G. Hanlin III
This is PBWiz, a library of assembly language and BASIC
routines for use with PowerBASIC version 2.x. The PBWiz
collection is copyrighted, but may be distributed as long as
the following conditions are obeyed:
All PBWiz files must be distributed together in unmodified
form. No files may be removed or added.
You use this library at your own risk. It has been tested by
me on my own computer, but I will not assume any responsibility
for any problems which PBWiz may cause you. If you do
encounter a problem, please let me know about it, and I will do
my best to verify and repair the error.
It is expected that if you find PBWiz useful, you will register
your copy. You may not use PBWiz routines in programs intended
for distribution unless you have registered. Registration
entitles you to receive the latest version of PBWiz, complete
with full source code in assembly language and BASIC. The
assembly code is designed for the MASM 6.0 assembler and may
require modifications for use with other assemblers. See
REGISTER.TXT for details on registration.
Warning: Unregistered use of PBWiz for more than 30 days may
cause the author to sing Gregorian chants under your window.
Don't let this happen to you!
Table of Contents page 2
Synopsis and Legal Info .................................... 1
Table of Contents .......................................... 2
Overview ................................................... 3
ANSI emulation ............................................. 4
Archives ................................................... 5
Dates and Times ............................................ 7
Disk Directories ........................................... 8
Equipment Info ............................................ 10
Extended Math ............................................. 14
Graphics .................................................. 17
Keyboard .................................................. 20
Memory (EMS) .............................................. 24
Memory (XMS) .............................................. 27
Mouse Support ............................................. 29
Strings ................................................... 32
Text-mode Video ........................................... 35
Credits ................................................... 40
Overview page 3
Any program that uses any of the PBWiz routines must DECLARE
them appropriately. To make this easy, I've created a single
file which contains all of the necessary declarations. Put
the following line at the top of your program to use it:
$INCLUDE "pbwiz.inc"
The DECLARE statements contained in this file tell PowerBASIC
how to behave when it runs into any PBWiz routines. They
don't actually load the routines. To do that, you must $LINK
the appropriate units. The names of the units to $LINK are
specified in each chapter of this manual.
Each chapter covers a specific unit or pair of units. If the
code is written in assembly language, the unit name ends in A.
If the code is written in PowerBASIC, the unit name ends in B.
So, for instance, the VIDEO units are called VIDEOA.OBJ and
VIDEOB.PBU. If the code for a chapter is written entirely in
one language, I don't bother with -A or -B suffix.
ANSI Emulation page 4
The ANSI emulator allows you to display text with ANSI codes
without any need for ANSI.SYS or other ANSI drivers. All you
have to do is bring the ANSI emulator into your program:
$LINK "ansiprin.pbu"
Replace any PRINT statements with a call to the ANSI emulator:
PRINT St$;
turns into
CALL AnsiPrint (St$)
Note that this does not include a carriage return and linefeed.
If you want one, you'll have to add it explicitly:
St$ = St$ + CHR$(13) + CHR$(10)
CALL AnsiPrint (St$)
Other ASCII codes are supported as well, including CHR$(7)
[bell], CHR$(8) [backspace], and CHR$(12) [formfeed].
For a list of ANSI display codes, see your DOS manual or check
with your local BBS.
Archives page 5
When I started in the microcomputer industry, there was a
small variety of file archivers, all (more or less)
compatible. They did not provide compression, which was
relegated to another large selection of more-or-less
compatible utilities. Then came SEA's ARC. It was very slow,
but it did compression as well as archiving, and included CRC
checks so you could know whether the files were intact. It
swept the BBS scene in short order, becoming the new
standard. A few other archivers competed on about a level
footing, providing only minor variances on the ARC theme. Then
SEA decided to sue one of its more successful competitors,
Phil Katz (PKARC). The end result was the ZIP standard... but
in the chaos resulting from the breaking of the ARC standard,
many other archivers came into being: ARJ, LZH, PAK, ZOO, and
so forth.
PBWiz helps resolve the confusion by providing a single set of
routines which allow you to view the contents of archives in
any of the above-mentioned formats: ARC, ARJ, LZH, PAK, ZIP, or
ZOO. It also handles self-extracting EXE files of the form
produced by LHARC. Only archive directories are provided at
this time. Other formats will also be added as they arise. If
you have details on the format of an archive that you'd like me
to add to PBWiz, please send them my way.
This unit requires the STRING unit (discussed later) as well.
To use it in your program, you need to include both units:
$LINK "stringa.obj"
$LINK "stringb.pbu"
$LINK "archives.pbu"
Viewing archive directories is handled in roughly the same
fashion as you might view a DOS file directory. This makes it
possible to treat an archive and a subdirectory in a similar
manner.
When you're looking for the first file in an archive, use the
FindFirstA function. You must specify the archive name and a
file name. The archive name may include a drive and path
specification, and does not need to have the archive
extension. If you leave off the extension, FindFirstA will use
the first archive it comes across that matches the rest of the
specification. Note that the archive specification may not
contain wildcards. In contrast, the search file name may not
contain drive or path specs, but may contain wildcards.
CALL FindFirstA (Archive$, Filename$, ErrCode%)
Archives page 6
If there are no files to be found, or if the archive
specification was bad, an error code will be returned. If
there was no error, there may well be more files to be found.
You can find each of them with FindNextA:
CALL FindNextA (ErrCode%)
Of course, just finding a matching file doesn't do you much
good unless you can retrieve information about it. You can use
any of the following routines to provide information about a
matched file:
Nam$ = GetNameA$
Dat$ = GetDateA$
Tim$ = GetTimeA$
CRC$ = GetCRCA$
StorageMethod$ = GetStoreA$
CALL GetSizeA (OriginalSize&, CurrentSize&)
When you're done viewing an archive, be sure to close it:
CALL CloseA
Let's try an example. Given that you've already written the
$LINK line as specified on the previous page, you could see
all of the files in an archive using a program like this:
CALL FindFirstA (Archive$, "*.*", ErrCode%)
DO UNTIL ErrCode%
PRINT GetNameA$
CALL FindNextA (ErrCode%)
LOOP
CALL FCloseA
This program fragment also assumes that you have set Archive$
to the name of an archive. It might be convenient to set it to
the command line for testing purposes:
Archive$ = UCASE$(LTRIM$(RTRIM$(COMMAND$)))
Dates and Times page 7
This unit allows you to validate and compare dates. It also
provides the day of the week, given the date. Dates may not be
before the year 1900. Date strings may be in the form
"01/01/91" or "01-01-1991" (the delimiter is not significant
and years may be two or four digits; two-digit years will be
assumed to be in the 20th century).
To use the routines in this unit, include the following line
at the top of your program:
$LINK "timedate.pbu"
Let's start off with date validation. It's often important to
know if a date entered into your program is a valid date.
IF GoodDate%(DateSt$) THEN PRINT "The date is valid."
It can also be helpful to know on which day of the week a given
date falls.
Day$ = WeekDay$(DateSt$)
There are many useful things you can accomplish by turning a
date into a number which represents that date (or vice versa).
This allows you to compare two dates, which is important if you
want to sort by date; find out what the date will be in a given
number of days, or what it was some number of days ago; find
the number of days between two dates; display a calendar; and
so forth. This is easy to do with PBWiz:
DateNr& = Date2Num&(DateSt$)
DateSt$ = Num2Date$(DateNr&)
The DateNr& represents the number of days since January 1,
1900. This is less than 65,535 for dates that go up to around
the year 2070 or so, so you may wish to store the dates in
compressed two-byte form if your required range of dates is not
that large:
CrunchDate% = CVI(LEFT$(MKL$(DateNr&), 2))
This can be reversed simply:
DateNr& = CVL(MKI$(CrunchDate%) + STRING$(2, 0))
Note that dates crunched this way are only useful for storage
purposes, since the numbers greater than 32,767 are stored as
negative numbers due to the signed integer format BASIC uses.
You must uncompress them before doing any comparisons or date
calculations. Still, for a savings of 50%, it may be worth the
hassle to convert back and forth.
Disk Directories page 8
This unit lets you read disk directories and retrieve the same
information the DIR command shows, plus the file attribute. A
string buffer is used to allow recursive directory searching.
To use it in your program, include the DirMgr unit:
$LINK "dirmgr.obj"
When you're looking for the first file in a directory, use the
FindFirstF function. You must provide a search filespec, which
may contain a drive and path specification and use wildcards.
You must also provide a search attribute, which may be any
combination of the following added together:
Normal 0 (nothing special)
Read Only 1 file can be read, but not written to
Hidden 2 file is "invisible"
System 4 special DOS system file
Subdirectory 16 subdirectory
Archive 32 (used by some backup utilities)
Note that you will always get all files that match any of your
search specs. For example, if your search attribute was 18,
you'd get normal files, hidden files, normal subdirectories and
hidden subdirectories. If you want to be more specific, you
will have to test the file attribute of the resulting file.
You must provide a string buffer for the directory search
routine. This buffer must be 64 characters long and should be
initialized before each call to FindFirstF. By using different
buffer strings, you can search more than one directory at a
time, or perform recursive searches through directories.
Buffer$ = SPACE$(64)
CALL FindFirstF (Buffer$, Filename$, ErrCode%)
If there are no files to be found, or if the file specification
was bad, an error code will be returned. If there was no
error, there may well be more files to be found. You can find
each of them with FindNextF:
CALL FindNextF (Buffer$, ErrCode%)
Of course, just finding a matching file doesn't do you much
good unless you can retrieve information about it. You can use
any of the following routines to provide information about a
matched file:
FilAttr% = GetAttrF%(Buffer$) ' file attribute
FilName$ = GetNameF$(Buffer$) ' file name
FilDate$ = GetDateF$(Buffer$) ' file date
FilSize& = GetSizeF&(Buffer$) ' file size
FilTime$ = GetTimeF$(Buffer$) ' file time
Disk Directories page 9
The file attribute can be most readily decoded with a series of
ANDs. For example, to test for a subdirectory, you'd use:
IF FilAttr% AND 16 THEN PRINT "subdirectory"
Let's put all these routines together and see what it takes to
make a quick'n'dirty DIR-style utility.
$INCLUDE "pbwiz.inc"
$LINK "dirmgr.obj"
SearchName$ = COMMAND$ ' get search spec from command line
Buffer$ = SPACE$(64) ' set up buffer
CALL FindFirstF(Buffer$, SearchName$, SearchAttr%, ErrCode%)
DO UNTIL ErrCode%
PRINT GetNameF$(Buffer$), GetSizeF&(Buffer$),
PRINT GetDateF$(Buffer$), GetTimeF$(Buffer$),
FilAttr% = GetAttrF%(Buffer$)
IF FilAttr% AND 1 THEN PRINT "Read Only ";
IF FilAttr% AND 2 THEN PRINT "Hidden ";
IF FilAttr% AND 4 THEN PRINT "System ";
IF FilAttr% AND 16 THEN PRINT "Subdirectory ";
IF FilAttr% AND 32 THEN PRINT "Backup ";
PRINT
CALL FindNextA (ErrCode%)
LOOP
Doesn't take much, does it? Now you can add disk directory
handling to your program with a bare minimum of effort!
Equipment Info page 10
The equipment unit gives you information about the computing
environment. This includes both installed software and
hardware. You can use the equipment information routines by
including this line in your program:
$LINK "equipmen.obj"
The first function allows you to determine if an "enhanced"
keyboard (101-key) is installed. It may not be able to figure
out what the keyboard is on some older not-quite-clone PCs, in
which case it will take the safe way out and report that there
is no enhanced keyboard. This function returns -1 if there is
an enhanced keyboard present, 0 if not.
Enhanced% = KbdType%
Want to know the type of processor (CPU) being used? Can do!
CPU% = Processor%
The results will be reported as a number which can be decoded
as follows:
0 NEC V20
1 8088 or 8086
2 80186
3 80286
4 80386
5 80486
Maybe you'd like to check for a CD-ROM drive:
Drives% = CDROM%
This tells you how many logical drives exist, if there is a
CD-ROM available. If not, it will return 0. Note that the
CD-ROM installation check conflicts with the GRAPHICS.COM
installation check for DOS 4.0, due to some screw-up at IBM or
Microsoft. I'm not yet sure whether DOS 5.0 is similarly
afflicted.
The number of floppy drives installed is retrieved like this:
Drives% = Floppies%
Equipment Info page 11
There may be up to four floppy drives in a system; however, the
AT CMOS data area only directly supports two. This makes it
easy to find out what kind of drives the first two are, but not
the second two. Oh well, guess we'll have to settle for what
we can get, right?
CALL FloppyType (Drive1, Drive2)
The results from FloppyType are returned as follows:
0 no drive
1 5 1/4" 360K
2 5 1/4" 1.2M
3 3 1/2" 720K
4 3 1/2" 1.44M
Result codes of 5-7 are available, but not yet defined. One
might guess that the 2.88M drive supported by DOS 5.0 will be
drive type 5. Has anybody seen one of those puppies yet?
New memory types sure have burgeoned over the years...
expanded, extended, and now XMS. There are routines to check
all of these:
BaseExt& = AllExtMem& ' extended memory installed
NowExt& = GetExtM& ' BIOS extended memory available
CALL GetEMSm (TotalPages%, FreePages%) ' expanded memory
CALL GetXMSm (LargestFree&, TotalFree&) ' XMS memory
When you're dealing with extended memory, whether it be
BIOS-type or using the XMS standard, the results are returned
in kilobytes. Multiply 'em by 1024 to convert to bytes. When
you're dealing with expanded memory (EMS), the results are in
pages of 16,384 bytes.
I might note, by the way, that Microsoft seems to have
intentionally crippled the XMS standard. It can only support a
maximum of 64 megabytes. This may seem like a lot now, but I
can remember when my first PC (a Compaq portable) was the awe
of the neighborhood with 384K RAM! A few years down the road,
the artificial limitations of XMS are going to be a nuisance.
A few more routines to get the versions of the EMS and XMS
drivers, if any:
CALL GetEMSv (MajorV%, MinorV%)
CALL GetXMSv (MajorV%, MinorV%)
These return the major and minor version numbers as two
separate integers. For example, EMS 4.0 would return major
version 4, minor version 0.
Equipment Info page 12
It's nice to know a little about the operating environment.
With the below routines, you can find out what the DOS version
is; what version of 4DOS, if any, is in use; and whether
Microsoft Windows is running.
CALL GetDOSv (MajorV%, MinorV%)
CALL Get4DOSv (MajorV%, MinorV%)
CALL WinCheck (MajorV%, MinorV%)
These return results as major and minor version numbers, as
discussed on the previous page. The Get4DOSv and WinCheck
routines return zeroes if 4DOS and Windows, respectively, are
not available.
There are a couple of curious features of GetDOSv to keep in
mind. If the version is 10 or higher, you're running in OS/2
compatibility mode. DOS version 10 is actually OS/2 1.0,
version 20 is OS/2 2.0, and so on. Secondly, if you're using
DOS 5.0, the version reported may not be 5.0-- DOS 5.0 can be
told to reply with a lower version number to allow some older
software (which checks for a specific DOS version) to run
properly.
One final routine that should be of some value is the one that
allows you to find out what kind of display is available. It
tells you the specific adapter and whether the display is color
or monochrome. There is one case in which it can be confused,
however-- if the adapter is CGA, the display is assumed to be
color, since there is no way for the computer to know any
differently. So, although this routine provides a good idea of
what is available, it would be a good idea to provide an option
to tell the program that a monochrome display is attached.
Microsoft normally uses "/B" for this purpose, so that might be
a good standard to stick with.
CALL GetDisplay (Adapter%, Mono%)
IF Mono% THEN
PRINT "Monochrome monitor"
ELSE
PRINT "Color monitor"
END IF
SELECT CASE Adapter%
CASE 1: PRINT "MDA"
CASE 2: PRINT "Hercules"
CASE 3: PRINT "CGA"
CASE 4: PRINT "EGA"
CASE 5: PRINT "MCGA"
CASE 6: PRINT "VGA"
END SELECT
Equipment Info page 13
Aside from some of the oldest semi-clones, it's possible to
find out what sort of machine you're using by looking at a
specific data byte. You can access this as follows:
Machine% = PCType%
The result will need decoding. Here are some known values:
255 PC or XT
254 XT
253 PCjr
252 PC AT
251 XT
250 PS/2 Model 30
249 PC Convertible
248 PS/2 Model 70 or 80
154 Compaq Portable
45 Compaq Portable
Likewise, all but some of the oldest semi-clones maintain a
BIOS date value which tells you how old the BIOS ROM is. This
can be retrieved with the following routine:
BIOSdate$ = PCDate$
If your program is running on one of the rare old machines
which don't maintain a valid BIOS date, this routine will
return "No Date " instead of an actual date.
As far as a program is concerned, DR DOS is essentially the
same as MS-DOS. Still, it's always nice to know what sort of
operating environment you have. You can find out whether your
program is running under DR DOS with the following function:
IF DRDOS% THEN PRINT "It's DR DOS" ELSE PRINT "MS-DOS"
The number of serial and parallel ports available can be
readily obtained:
PRINT "Serial ports: "; CommPorts%
PRINT "Parallel ports: "; PrtPorts%
Extended Math page 14
The extended math unit provides an expression evaluator and
extensions to BASIC's math. The math extensions include
hyperbolic and inverse trig functions, a few handy constants,
conversions, and more. You can use the new math routines by
including these lines at the top of your program:
$LINK "extmatha.obj"
$LINK "extmathb.pbu"
The expression evaluator allows you to find the result of an
expression contained in a string. Normal algebraic precedence
is used, e.g. 4+3*5 evaluates to 19. The usual numeric
operators (*, /, +, -, ^) are supported (multiply, divide, add,
subtract, and raise to a power). Use of negative numbers is
just fine, of course. Parentheses for overriding the default
order of operations are also supported.
You may use either a double asterisk ("**") or a caret ("^")
symbols to indicate exponentiation.
The constant PI is recognized, as are the following functions:
ABS absolute value INT integer
ACOS inverse cosine LOG natural log
ASIN inverse sine SIN sine
ATAN inverse tangent SQR square root
COS cosine TAN tangent
FRAC fraction
Trig functions expect angles in radians.
To evaluate an expression, you pass it to the evaluator as a
string. You will get back either an error code or a
single-precision result. Try this example to see how the
expression evaluator works:
$STACK 8192
$INCLUDE "pbwiz.inc"
$LINK "extmatha.obj"
$LINK "extmathb.pbu"
DO
INPUT "Expression? "; Expr$
IF LEN(Expr$) THEN
CALL Evaluate (Expr$, Result!, ErrCode%)
IF ErrCode% THEN
PRINT "Invalid expression. Error = "; ErrCode%
ELSE
PRINT "Result: "; Result!
END IF
END IF
LOOP WHILE LEN(Expr$)
END
The expression evaluator uses recursion and requires more than
the default amount of stack space, which is why the $STACK
metacommand is used. See CALC.BAS for a working demo.
Extended Math page 15
The new math functions are pretty much self-explanatory, so
I'll just list them here. A few general notes are given on the
next page.
Result! = ArcCosHS!(Nr!) ' inverse hyperbolic cosine
Result! = ArcSinHS!(Nr!) ' inverse hyperbolic sine
Result! = ArcTanHS!(Nr!) ' inverse hyperbolic tangent
Result! = ArcCosS!(Nr!) ' arc cosine (1 >= Nr >= -1)
Result! = ArcSinS!(Nr!) ' arc sine (1 >= Nr >= -1)
Result! = ErfS!(Nr!) ' error function
Result! = FactS!(Nr%) ' factorial
Result! = CotS!(Nr!) ' cotangent
Result! = CscS!(Nr!) ' cosecant
Result! = SecS!(Nr!) ' secant
Result! = CosHS!(Nr!) ' hyperbolic cosine
Result! = SinHS!(Nr!) ' hyperbolic sine
Result! = TanHS!(Nr!) ' hyperbolic tangent
Result! = Deg2RadS!(Nr!) ' convert degrees to radians
Result! = Rad2DegS!(Nr!) ' convert radians to degrees
Result! = Cent2Fahr!(Nr!) ' centigrade to Fahrenheit
Result! = Fahr2Cent!(Nr!) ' Fahrenheit to centigrade
Result! = Kg2Pound!(Nr!) ' convert kilograms to pounds
Result! = Pound2Kg!(Nr!) ' convert pounds to kilograms
Pi! = PiS! ' the constant "pi"
e! = eS! ' the constant "e"
Result! = CeilS!(Nr!) ' smallest integer >= Nr
Result! = FloorS!(Nr!) ' largest integer <= Nr
Result# = ArcCosHD#(Nr#) ' inverse hyperbolic cosine
Result# = ArcSinHD#(Nr#) ' inverse hyperbolic sine
Result# = ArcTanHD#(Nr#) ' inverse hyperbolic tangent
Result# = ArcCosD#(Nr#) ' arc cosine (1 >= Nr >= -1)
Result# = ArcSinD#(Nr#) ' arc sine (1 >= Nr >= -1)
Result# = ErfD#(Nr#) ' error function
Result# = FactD#(Nr%) ' factorial
Result# = CotD#(Nr#) ' cotangent
Result# = CscD#(Nr#) ' cosecant
Result# = SecD#(Nr#) ' secant
Result# = CosHD#(Nr#) ' hyperbolic cosine
Result# = SinHD#(Nr#) ' hyperbolic sine
Result# = TanHD#(Nr#) ' hyperbolic tangent
Result# = Deg2RadD#(Nr#) ' convert degrees to radians
Result# = Rad2DegD#(Nr#) ' convert radians to degrees
Pi# = PiD# ' the constant "pi"
e# = eD# ' the constant "e"
Result# = CeilD#(Nr#) ' smallest integer >= Nr
Result# = FloorD#(Nr#) ' largest integer <= Nr
Extended Math page 16
Result% = GCDI%(Nr1%, Nr2%) ' greatest common denominator
Result% = RotateL%(Nr%, Count%) ' rotate left
Result% = RotateR%(Nr%, Count%) ' rotate right
Result% = ShiftL%(Nr%, Count%) ' shift left
Result% = ShiftR%(Nr%, Count%) ' shift right
Result& = GCDL&(Nr1&, Nr2&) ' greatest common denominator
Result& = RotateLL&(Nr&, Count%) ' rotate left
Result& = RotateRL&(Nr&, Count%) ' rotate right
Result& = ShiftLL&(Nr&, Count%) ' shift left
Result& = ShiftRL&(Nr&, Count%) ' shift right
The ceiling and floor functions are generally used in rounding.
Like BASIC's trig functions, the PBWiz trig functions expect
the angle to be in radians. Conversion functions are provided
in case you prefer degrees.
Note that there is no ArcTanS! or ArcTanD# function for the
simple reason that BASIC supplies an ATN function.
Constants are expressed to the maximum precision available.
If you are not familiar with variable postfix symbols, here's a
brief summary:
Symbol Meaning Range (approximate)
------ -------- -------------------------------
% integer +- 32767
& long integer +- 2 * 10^9
! single precision +- 1 * 10^38
# double precision +- 1 * 10^308
See PowerBASIC's online help for further details.
Graphics Support page 17
I was rather surprised to find that PowerBASIC lacks support
for one of the standard VGA modes-- SCREEN 13, the 320x200
256-color mode. I immediately decided to add support for that
mode: dots, lines, boxes, polygons, and (of course) text.
While I was at it, I thought I'd add support for a nonstandard
VGA mode: 360x480 in 256 colors. This mode will work on almost
any plain VGA system, although it might not work on some older
not-quite-compatible setups. SuperVGA modes are also supported
for adapters based on the popular Tseng 4000 chip set.
13 320x200, 256 colors, 40x25 text, any VGA
N0 360x480, 256 colors, 45x60 text, almost any VGA
N5 ? x ? , 256 colors, ? x ? text, Tseng 4000 SVGA
The graphics routines all use the same nomenclature: a G,
followed by a mode number, followed by the specific name. This
generic naming convention is used so that this chapter can
refer to all available modes. For example, if I say "G#Color"
and you're using mode 13, you'd actually use "G13Color" in your
program. Ok?
There are two sets of routines for each mode-- the ones written
in ASM and the ones written in BASIC. The file names for these
will be suffixed with "A" for ASM and "B" for BASIC. For
instance, to use the SCREEN 13 routines, you would add the
following at the top of your program:
$LINK "g13a.obj"
$LINK "g13b.pbu"
The first thing you will always have to do is to put the screen
into the proper mode. This is done with the G#Mode routine:
CALL G#Mode (Graphics%)
Use 0 to switch to text mode or any other value to switch to
graphics mode.
In the case of the N5 routines, you need to initialize the mode
information before your first call to GN5Mode. That's done by
specifying the BIOS mode number and the screen resolution:
CALL GN5Init (BIOSMode%, PixelsWide%, PixelsHigh%)
The BIOS mode, width, height, and amount of video memory
required to support the mode may be any of the following:
&H2D 640 x 350 256k
&H2E 640 x 480 512k
&H2F 640 x 400 256k
&H30 800 x 600 512k
&H38 1024 x 768 1M
Graphics Support page 18
One difference between BASIC and BasWiz is that, instead of
each "draw" command requiring a color parameter as in BASIC,
the PBWiz library provides a separate color command:
CALL G#Color (Foreground%, Background%)
The "foreground" color is used by all graphics routines. The
background color is used by the G#Cls routine. Both foreground
and background colors are used in by G#Write and G#WriteLn.
Here is a list of the corresponding routines, first BASIC, then
PBWiz (replace the "#" with the appropriate mode number):
' get the color of a specified point
colour% = POINT(x%, y%)
colour% = G#GetPel%(x%, y%)
' set the color of a specified point
PSET (x%, y%), colour%
CALL G#Color (colour%, backgnd%): CALL G#Plot (x%, y%)
' draw a line of a specified color
LINE (x1%, y1%) - (x2%, y2%), colour%
CALL G#Color (colour%, backgnd%)
CALL G#Line (x1%, y1%, x2%, y2%)
' draw a box frame of a specified color
LINE (x1%, y1%) - (x2%, y2%), colour%, B
CALL G#Color (colour%, backgnd%)
CALL G#Box (x1%, y1%, x2%, y2%, 0)
' draw a box of a specified color and fill it in
LINE (x1%, y1%) - (x2%, y2%), colour%, BF
CALL G#Color (colour%, backgnd%)
CALL G#Box (x1%, y1%, x2%, y2%, 1)
' clear the screen and home the cursor
CLS
CALL G#Cls
' get the current cursor position
Row% = CSRLIN: Column% = POS(0)
CALL G#GetLocate (Row%, Column%)
' set the current cursor position
LOCATE Row%, Column%
CALL G#Locate (Row%, Column%)
' display a string without a carriage return and linefeed
PRINT St$;
CALL G#Write (St$)
' display a string with a carriage return and linefeed
PRINT St$
CALL G#WriteLn (St$)
Graphics Support page 19
If you need to print a number rather than a string, just use
the BASIC function STR$ to convert it. If you don't want a
leading space, use this approach:
St$ = LTRIM$(STR$(Number))
The PBWiz library has other routines which have no BASIC
equivalent. One allows you to get the current colors:
CALL G#GetColor (Foreground%, Background%)
Circles and ellipses can be drawn with the Ellipse routine.
This is similar to the BASIC CIRCLE statement. You specify the
center of the ellipse (X,Y), plus the X and Y radius values:
CALL G#Ellipse (CenterX%, CenterY%, XRadius%, YRadius%)
A circle is an ellipse with a constant radius. So, to draw a
circle, just set both radius values to the same value.
As well as the usual points, lines, and ellipses, PBWiz also
allows you to draw regular polygons: triangles, squares,
pentagons, hexagons, and so on.
CALL G#Polygon (X%, Y%, Radius%, Vertices%, Angle!)
The X% and Y% values represent the coordinates of the center of
the polygon. The Radius% is the radius of the polygon (as if
you were fitting it into a circle). Vertices% is the number of
angles (also the number of sides) for the polygon to have.
Angle! specifies the rotation of the polygon, and is specified
in radians.
Keyboard Control page 20
The keyboard is not a particularly exciting or glamorous
device. In fact, we tend to forget about it except when it
gets in the way. Sometimes it's a hardware problem-- squishy
or clacking keys, or perhaps a commonly-used key placed in an
out-of-the-way location. Then again, sometimes it's the
software that's the problem. There are many aspects of
keyboard control, not all of which are necessarily related to
input. This unit will let you handle the keyboard in ways you
may not have realized were possible. Better yet, it can help
make keyboard control easier than the users of your programs
dreamed possible. It all starts with the one little line:
$LINK "keyboard.obj"
Let's start out with keyboard output. Yep, not input--
output. We can stuff up to 15 keys into the keyboard buffer.
Why would we ever want to do this? Perhaps to allow your
program to pop-up a TSR automatically, to start another program
after your program ends, or for creating key macros. You can
enter extended key codes (such as function keys) by using
CHR$(0) before the scan code.
CALL TypeIn (St$)
The usual keyboard action is somewhat sluggish. We can make it
a lot crisper by changing the key repeat rate and the delay
before repeating begins. This will work on ATs, but not PC/XT
systems.
CALL SpeedKey (RepDelay%, RepRate%)
The delay may be 0-3 (1 by default):
0 250 milliseconds
1 500 milliseconds
2 750 milliseconds
3 1 second
The repeat rate may be 0-31 (11 by default). The larger the
number, the slower the speed-- 0 is around 30 cps, and 31 is
around 2 cps.
I generally prefer to have the keyboard cranked up to full
speed, using RepDelay% and RepRate% both set to zero. This may
be a bit too zippy for some people. Experiment with it to see
what you like best.
Of course, there may be reasons to make keyboard repeat less
sensitive instead! That might be a good idea in programs
written for small children, for example. You can adjust the
keyboard equally well in either direction.
Keyboard Control page 21
PowerBASIC allows you to control one of the keys which can
interrupt your program, namely the Break key. There's another
dangerous key which PBWiz allows you to control-- the PrtSc
(PrintScreen) key. PrtSc may not seem like a hazard at first
glance, but if it's pressed by accident with no printer ready,
or in a graphics mode which PrtSc doesn't understand how to
print, the results can be pretty messy. So, we let you turn it
off or back on:
CALL SetPrtSc (PrtScON%)
Use 0 to turn it off or anything else to turn it back on. If
you turn off PrtSc, you MUST remember to turn it back on again
before your program ends! Otherwise, an interrupt vector will
point into nowhere, causing probable chaos the next time PrtSc
is pressed.
Regardless of whether you've turned the PrtSc key off, you can
print the screen yourself just as if PrtSc had been pressed:
CALL PrintScreen
Now here's a strange one for you. When IBM brought out the
101-key keyboard, called the "enhanced" keyboard, they did
something bizarre to the BIOS. They still allowed old keyboard
calls to work, but they filtered out the new key codes so no
one would see them. This made sure that no one would be able
to use the capabilities of the "enhanced" keyboard without
rewriting their programs. So, the keyboard has been around for
years, and there are still few programs that even notice when
you press F11. PowerBASIC v2.1 does not support the enhanced
keyboard at all. Fortunately, PBWiz -does-. You can find out
if an enhanced keyboard is installed with the KbdType%
function, which is in the Equipment unit.
If there is an enhanced keyboard installed, you can activate it
like so:
CALL SetEnhKbd (Enhanced%)
With enhanced keyboard support activated, all key requests that
used the old services are translated to the new services. So,
SetEnhKbd affects INKEY$ and other BASIC functions as well as
other PBWiz keyboard routines. Note that you MUST deactivate
enhanced keyboard support before ending your program.
Otherwise, an interrupt vector will point into nowhere,
probably causing a crash on the next keypress!
Keyboard Control page 22
Speaking of INKEY$, I have a neat little function for you. It
works like INKEY$, but it doesn't remove the key from the
keyboard buffer:
ky$ = ScanKey$
How is this handy? Well, let's suppose you're writing
something that will work like the DIR or TYPE commands in DOS.
It will display what may be a long listing, which you'd like to
be able to pause with Ctrl-S or cancel with Ctrl-C. Trouble
is, if you use INKEY$ and the user was using "type ahead" to
store another command, you've just wiped out his command while
looking for those control codes. With ScanKey$, you can check
the key nondestructively.
On the other hand, if you're about to request important input,
you may not want to chance having it answered from results of
the keyboard buffer-- could be that the user meant those keys
for another purpose. In that case, it's a good approach to
clear out the keyboard buffer just before the input:
CALL ClearKbd
No keyboard unit would be complete without a selection of
routines to check the shift states and get or set the keyboard
toggles. Let's start with the toggles, which are so called
because they get toggled from one state to another:
PRINT "Caps Lock : ";
IF CapsOn% THEN PRINT "ON" ELSE PRINT "OFF"
PRINT "Insert : ";
IF InsertOn% THEN PRINT "ON" ELSE PRINT "OFF"
PRINT "Num Lock : ";
IF NumOn% THEN PRINT "ON" ELSE PRINT "OFF"
PRINT "Scroll Lock: ";
IF ScrollOn% THEN PRINT "ON" ELSE PRINT "OFF"
You can also turn the toggles off or on. It's courteous to
restore the original toggle states once you end your program,
so you might want to save the original values for that
purpose. Then again, I guess that doesn't apply if your
program is designed for the specific purpose of setting the
toggles!
CALL SetCaps (CapsLock%)
CALL SetInsert (InsertKey%)
CALL SetNum (NumLock%)
CALL SetScroll (ScrollLock%)
Does anyone actually use ScrollLock for anything? Just
wondering...
Keyboard Control page 23
The shift keys are unique in many respects. They don't return
codes that can be detected with INKEY$ or stuffed into the
keyboard buffer; several can be pressed at the same time; and
they don't repeat. You can detect 'em with PBWiz, at any rate:
IF AltPress% THEN PRINT "An ALT key is pressed."
IF CtrlPress% THEN PRINT "A CTRL key is pressed."
IF ShiftPress% THEN PRINT "A SHIFT key is pressed."
IF LAltPress% THEN PRINT "The LEFT ALT key is pressed."
IF LCtrlPress% THEN PRINT "The LEFT CTRL key is pressed."
IF LShiftPress% THEN PRINT "The LEFT SHIFT key is pressed."
IF RAltPress% THEN PRINT "The RIGHT ALT key is pressed."
IF RCtrlPress% THEN PRINT "The RIGHT CTRL key is pressed."
IF RShiftPress% THEN PRINT "The RIGHT SHIFT key is pressed."
NOTE that LAltPress%, LCtrlPress%, RAltPress%, and RCtrlPress%
are ONLY available for enhanced keyboards. They will not
return useful results on older keyboards.
Memory (EMS) page 24
This unit provides support for expanded memory. It will work
with older EMS and EEMS drivers as well as the current EMS 4.0
standard, with the exception of one or two routines (as noted)
which take advantage of new capabilities.
Expanded memory may be present on any type of computer, from
8088 PCs to 80486 ATs. It usually comes as a hardware board
with a software driver for older machines; on ATs, it may be
implemented using only a software driver which converts it from
extended memory. Drivers have also been written which make a
hard disk function as EMS memory. This broad range of use
makes EMS support invaluable to programs which need extra
memory. EMS can theoretically support up to 1 gigabyte of RAM,
although the documented limit as of v4.0 was only 32 megabytes.
This unit is called EMS. You can access it by including this
line at the top of your program:
$LINK "ems.obj"
Of course, the first thing you need to know is whether any EMS
memory actually exists:
IF EMSexists% THEN PRINT "EMS exists"
The EMS version may also be retrieved:
CALL EMSver (MajorV%, MinorV%)
It would be a good idea to check the EMS version if you plan to
use any features which are only available as of EMS 4.0, such
as reallocation.
Besides a mere existence and version checks, you will want to
know how much EMS is available:
PRINT "Total EMS installed: "; EMStotal%
PRINT "Free EMS memory : "; EMSfree%
If you actually tried the above two lines, you would get a pair
of values which don't seem to mean much. The trick is to
multiply them by 16,384 to convert them to bytes. This is
because EMS memory is always accessed in pages of 16k bytes
each. Any time you are dealing with a quantity of EMS memory,
the quantity will be specified as a number of pages.
Memory (EMS) page 25
Before we get into the mechanics of accessing EMS memory, I'd
like to bring up an optional routine which can improve access
speed. It should not be used if your program accesses EMS
using routines other than the ones included here in PBWiz. If
you only use these EMS routines, though, you will find that it
makes some kinds of memory accesses faster. Use 0 for normal
(slow) mode. Do not use optimization if you are using more
than one EMS handle!
CALL EMSopt (Fast%)
Ok, let's get down to the nitty gritty. (Where did that
expression come from, anyway?!) When you allocate EMS memory,
you specify the number of pages you want, which must be at
least 1. If the allocation is successful, you are returned a
"handle" which you can use to access the allocated memory.
Otherwise, you get back an error code.
CALL EMSopen (Pages%, Handle%, ErrCode%)
There are a limited number of handles available under some EMS
drivers. The EMS spec, as of v4.0, allowed for a maximum of
255 handles, and it's not unusual for a driver to support only
20 or so. Bearing in mind that some of these handles may be
used up by other applications, such as RAMdisks and caches,
this really doesn't allow much leeway. Try to use as few
handles as possible! You may well need to store multiple
values in different areas of the memory allocated for a single
handle, rather than allocating a new area of memory for each
value.
Suppose you find you need more memory than you first
allocated? Or maybe less memory? Well, provided EMS 4.0 or
later is in use, you can reallocate the block:
CALL EMSresize (Handle%, Pages%, ErrCode%)
There is no way to reallocate memory under older versions of
EMS. About the best you could do in that case would be to
allocate a new area of the desired size, copy over the relevant
data from the old area, and then deallocate the original area.
Of course, this assumes that there is enough memory available
to hold both areas, at least temporarily.
When you are finished using EMS, you must be sure to return the
memory you allocated to the system. It is IMPORTANT to do this
before ending your program. Otherwise, the memory you
allocated will be "lost" until the next time you boot the
computer. Return the memory for each handle as follows:
CALL EMSclose (Handle%)
Memory (EMS) page 26
Hmmmm... we've covered EMS detection, status info, allocating
memory, freeing memory, resizing memory... what's missing
here? Ah! We haven't discussed how to actually access the
memory!
Accessing EMS memory is quite simple, but it involves a couple
of steps. EMS is mapped into a 64k block in the low area of
memory (the area under 1M, which can be directly accessed).
Since a page is 16k, the EMS block can hold up to four pages at
a time. To transfer data between normal memory and EMS memory,
you must map the appropriate page(s) of EMS into the EMS
block. This is done like so:
CALL EMSmap (Handle%, PPage%, VPage%)
The PPage% is the physical page, that is, the page number
within the EMS block (0-3). The VPage% is the virtual page,
which is the number of a page within the EMS memory associated
with Handle%.
Once you've mapped the desired virtual page into a physical
page, it can be accessed using standard PowerBASIC memory
commands, such PEEK$ and POKE$. First, set the segment:
DEF SEG=EMSseg&
The offset within a page may be 0-16,383. To get the
appropriate offset within the EMS block, you must add the
offset of the page itself. This may be calculated as follows:
DataOffset& = OffsetWithinPage% + PageNumber% * 16384&
One final note: for best compatibility, it would be good to
avoid using physical page 3 (the last 16k of the EMS block).
Some EMS drivers don't handle this page with complete accuracy,
for technical reasons I'm not going to get into right now.
Memory (XMS) page 27
This unit provides support for XMS extended memory. It won't
work with extended memory unless an XMS driver is present.
Extended memory is only present in AT-class computers. It is
not available on older PCs. An XMS driver must also be used.
XMS drivers are included with MS-DOS 5.0 and Windows 3.0, among
other things, so this is not a major limitation. XMS can
address a maximum of 64 megabytes.
This unit is called XMS. You access it by including this line
at the top of your program:
$LINK "xms.obj"
The first thing to check is whether any XMS memory exists:
IF XMSexists% THEN PRINT "XMS exists"
The XMS version may also be retrieved:
CALL XMSver (MajorV%, MinorV%)
The amount of XMS memory available may be reported in either of
two different ways: total amount and largest available block.
PRINT "Total XMS free : "; XMStfree&
PRINT "Largest free block: "; XMSlfree&
XMS memory is manipulated in terms of 1,024 byte blocks, so the
amount of free memory is reported in kilobytes. Any time you
are dealing with a quantity of EMS memory, the quantity will be
specified as a number of 1K blocks, except as otherwise noted.
Memory (XMS) page 28
When you allocate XMS memory, you specify the number of
kilobytes that you want. This may be 0-65535, in theory.
Dunno, I don't have 64M RAM <grin>. If the allocation is
successful, you are returned a "handle" which you can use to
access the allocated memory. Otherwise, you get back an error
code.
CALL XMSopen (KBytes&, Handle%, ErrCode%)
There are a limited number of handles available, although the
number can be controlled somewhat by a driver parameter. It's
probably best to use as few handles as possible, to avoid
running out. You may well want to store multiple values in
different areas of the memory allocated for a single handle,
rather than allocating a new area of memory for each value.
Suppose you find you need more memory than you first
allocated? Or maybe less memory? Just reallocate the block:
CALL XMSresize (Handle%, KBytes&, ErrCode%)
When you are finished using XMS, you must be sure to return the
memory you allocated to the system. It is IMPORTANT to do this
before ending your program. Otherwise, the memory you
allocated will be "lost" until the next time you boot the
computer. Return the memory for each handle as follows:
CALL XMSclose (Handle%)
To transfer data between normal memory and XMS memory, you must
provide the segment and offset of the normal memory area (use
the PowerBASIC functions VARSEG and VARPTR to find the address
of a variable). The position within XMS memory is specified as
a long-integer offset starting at zero.
CALL XMSread (Handle%, Posn&, Bytes&, DSeg%, DOfs%)
CALL XMSwrite (Handle%, Posn&, Bytes&, DSeg%, DOfs%)
Note that the Bytes& to transfer must be an EVEN NUMBER. It is
not restricted to 64k, however, so you can transfer a great
deal of data with these routines.
The XMS spec guarantees a "reasonable" number of interrupt
windows during a transfer; however, it is possible that you
might experience some communications dropouts if you do large
transfers during high-speed telecommunications. If this is
expected to happen, test it carefully.
Mouse Support page 29
The mouse unit provides full-featured mouse support. You can
see if a mouse is available and how many buttons it has, get
the cursor position (either the current position or the
position at the last press or release of a specified button),
set the cursor position, change the cursor, set the mouse
range, get hardware information about the mouse, and so on.
This unit is called MOUSE, so you access it by including the
following line at the top of your program:
$LINK "mouse.obj"
There are two unusual mouse modes to be aware of. One is text
mode, which is mapped to a 640x200 virtual display. So, to
convert the results to text format, you need to divide the
cursor position by eight and add one. To convert from text
format, subtract one and multiply by eight.
The second unusual mode is 320x200 CGA mode, which is also
mapped to 640x200. To convert the coordinates to this mode,
divide X by two. To convert from this mode, multiply the X
coordinate by two.
All other modes use the actual display coordinates instead of a
bizarro virtual screen. Why the peculiar CGA and text modes?
Well, evidently Microsoft never thought there'd be any video
adapters besides MDA and CGA, and decided to create a single
virtual screen size that worked for all modes. Not a bad idea,
I guess, but rather shortsighted. Oh well.
One other nuisance that you may run into is that the mouse
cursor can't be directly turned on or off. A "cursor
visibility" count is maintained-- if the mouse cursor was
turned on twice, you'll need to turn it off twice before it
will actually disappear.
Before using the mouse, you must initialize it. The
initialization routine also checks to make sure that a mouse is
installed and tells you how many buttons it has. It's best to
initialize the mouse after setting the screen mode, so the
mouse driver understands what mode you're using. Not all mouse
drivers support all screen modes, but you can reasonably expect
any current mouse driver to support MDA, CGA, EGA, and VGA.
Hercules graphics mode is rarely supported, as it must be set
through direct hardware access rather than the standard
techniques, so the mouse driver has little way of knowing that
you've changed the mode.
The mouse routines will work equally well with two-button or
three-button rodents. The middle button functions will return
0 with two-button mice.
Mouse Support page 30
I won't go into great detail on these routines, because they're
pretty much self-explanatory. The mouse is a fairly easy
device to deal with.
You can initialize the mouse driver like so:
Buttons% = MouseInit%
This returns the number of mouse buttons available. If there
is no mouse, zero will be returned. Initialize the mouse after
setting the screen mode.
You can make the mouse cursor visible or invisible. It will
function just as well in either state. See the previous page
for some quirks.
CALL MouseShow ' show the cursor
CALL MouseHide ' hide the cursor
There are many ways to get the mouse cursor position. You can
get the current position, the position at which the mouse was
located when a particular button was pressed, or the position
when a button was released. If you choose a past position, you
can also find out how many presses or releases of the button
have taken place since you last checked.
X% = MouseWhereX% ' current X coordinate
Y% = MouseWhereY% ' current Y coordinate
CALL MouseLClick (Count%, X%, Y%) ' left presses & posn
CALL MouseMClick (Count%, X%, Y%) ' middle presses & posn
CALL MouseRClick (Count%, X%, Y%) ' right presses & posn
CALL MouseLRelease (Count%, X%, Y%) ' left releases & posn
CALL MouseMRelease (Count%, X%, Y%) ' middle releases & posn
CALL MouseRRelease (Count%, X%, Y%) ' right releases & posn
If you'd prefer to find out which buttons are currently
pressed, no problem:
Pressed% = MouseLButton% ' whether left button is pressed
Pressed% = MouseMButton% ' whether middle button is pressed
Pressed% = MouseRButton% ' whether right button is pressed
Of course, you can also set the cursor location:
CALL MouseLocate (X%, Y%) ' set the mouse cursor position
The mouse cursor range can be restricted to a given area of the
screen. This area is expressed by giving the upper left corner
and lower right corner of the rectangular area to which to
restrict the cursor.
CALL MouseWindow (X1%, Y1%, X2%, Y2%)
Mouse Support page 31
There are a variety of cursor shapes available for graphics
mode:
0 hourglass ("please wait, program is working" symbol)
1 pointing arrow (default)
2 pointing hand
3 crosshair
4 target (box in a box)
5 grabbing hand
If you have ideas for more, let me know and I'll see what I can
do. Cursor shapes are not unduly difficult to define.
The cursor image is set like so:
CALL MouseCursorG (CursorNr%)
Strings page 32
One of the true strengths of BASIC lies in its powerful string
handling capability. I'd be remiss if I didn't provide
extensions which improve it even further. This unit can be
accessed by including this line at the top of your program:
$LINK "stringa.obj"
$LINK "stringb.pbu"
The simplest of the PBWiz string routines may seem somewhat
whimsical, but it has proven useful to me on several occasions
in the past. It reverses the order of characters in a string.
CALL Reverse (St$)
One of the places this has come in useful is in searching a
string from the end-- a reverse INSTR routine:
CALL RInstr (MainSt$, SubSt$, Posn%)
Rather than returning the first occurrence of a substring
within a main string, it returns the last occurrence. Another
handy string search allows you to search for various types of
characters, rather than a specific substring:
CALL TInstr (MainSt$, Types%, Posn%)
The type(s) may be specified using any combination of the
following. Just add them together.
1 alphabetic
2 numeric
4 symbolic
8 control
16 graphics
32 space
Since you can search for any specific types, you can also
readily invert the search to look for any characters that are
NOT of a given type or types:
Types% = NOT Types%
This gives you complete control. A typical use for TInstr
might be to clean up user input and make sure that it's valid.
It's also good for parsing.
Strings page 33
Another routine that is useful for cleaning up and parsing user
input is called Crunch. It allows you to eliminate adjacent
duplicates of a character or list of characters. One use for
this, for instance, would be to eliminate repeated spaces,
converting an input string from "*.* *.BAK /B" to a more
manageable "*.* *.BAK /B".
Result$ = Crunch$(St$, CharList$)
There are a pair of routines that you'll find valuable if you
need to check the validity of a string. These are designed to
be compatible with the Xmodem and Ymodem file transfer
protocols, so you can use them for error checking in
telecommunications as well.
Chk% = CheckSum% (St$)
CALL CRC16 (St$, HiCRC%, LoCRC%)
Another pair of string routines provide a simple encryption
and decryption system for text. The method used is not
particularly secure but are very fast and will be adequate for
many purposes. As always, it helps to use a long and/or
complex password.
CALL Cipher (St$, Password$)
CALL CipherP (St$, Password$)
Both of these routines will encipher text on the first
run-through and decipher on the second, so you can use the same
routine either to encrypt or decrypt a message. They are
different in one respect: the encrypted result of Cipher may
contain control characters, so it can't be used in a plain
sequential-access file. The CipherP routine does not allow use
of extended ASCII characters (CHR$(128) - CHR$(255)), as it
sets the high bit on each character after encrypting it. This
causes the results of CipherP to be printable (and useful in
sequential-access files), although they will look very strange.
The strings are encrypted (or decrypted) in place. This
provides a certain extra measure of security for encryption--
the original plaintext strings are not left floating around in
memory where someone might see them.
One function is as much a file manipulation routine as it is a
string function. It allows you to compare a file name to a
file pattern (which may contain wildcards) to see if they
match. Only bare filespecs are supported-- you may not use
drive or path specifications in the names.
IF MatchFile% (Pattern$, Filename$) THEN PRINT Filename$
Strings page 34
The MatchFile function can be used in creating your own
DOS-style utilities: DIR, COPY, and so forth. Besides the
usual "accept file if it matches" approach, it can also be used
to implement the opposite: "exclude file if it matches." This
gives you more flexibility than DOS itself supplies.
The PowerBASIC compiler provides a very nice function called
Extract$. This allows you to retrieve a substring running from
the left side of a main string to a specified character
delimiter. Not bad, but it might be handy to be able to grab a
numbered substring from any part of a main string, and to be
able to use a substring delimiter. For instance, you might
load a record from a database which contains an address, where
each line is delimited by a carriage return and linefeed.
Rather than mucking around with Extract$, which really wasn't
designed with that sort of thing in mind, you'd do better to
use the PBWiz function called DelimExtract$:
SubSt$ = DelimExtract$(St$, Delimiter$, Index%)
The index starts at 1 with the first substring. If you choose
the index of a substring which doesn't actually exist, a null
string will be returned.
Text-mode Video page 35
Routines in this unit may be accessed if you include these
lines at the top of your program:
$LINK "videoa.obj"
$LINK "videob.pbu"
The graphical interface has become a "sexy" thing to have
these days, but there are still many good reasons to work in
text mode. It's relatively fast, it works on all monitors,
and it's often the most appropriate choice. Graphics is
overkill for many applications. Besides, you can't redirect
graphics to a file or to the printer.
Come to think of it, text displayed by the PRINT statement
can't be redirected either. Fortunately, we can fix that.
All it takes is sending the output through DOS:
CALL DOSPrint (St$) ' print to the current output device
A nice thing about DOS output is that ANSI display codes will
work if you have an ANSI driver (such as ANSI.SYS) installed.
If you're working with existing text, such as captured output
from a BBS or from an ANSI art program like TheDraw, you can
just use the DOSPrint routine to handle it. If you're looking
to do your own ANSI output, though, there's an easier way than
looking up the individual codes and sending 'em out one at a
time. The following routines send the appropriate ANSI codes
to the current DOS output device:
CALL DOSCls ' clear the screen
CALL DOSColor (Fore%, Back%) ' set the screen colors
CALL DOSLocate (Row%, Column%) ' set the cursor position
The primary advantage of DOS output is that it can be
intercepted. It normally goes to the screen, but you can
redirect it to a file, printer, or comm port, among other
things. You can also be sure that DOS output will work
reasonably in a multitasking environment like DESQview or
Windows, instead of messing up the screen. The disadvantage
is that DOS output is fairly slow. It's great for command
line utilities, but not if you plan to do any fancy screen
work. For that, you probably want direct-access techniques.
The direct screen access provided by PBWiz is rude, crude...
and faster than a greased euphemism. It can't be redirected,
doesn't handle control codes, and won't even update the cursor
position. In return for this lack of amenities, it gives you
two very valuable things: complete control and raw speed.
Text-mode Video page 36
The direct-access replacement for PRINT works like so:
CALL XQPrint (St$, Row%, Column%, Attr%)
Now, you might guess that St$ is the text to print, and Row%
and Column% are where to print it. The Attr% may seem a bit
more opaque. The Attr% is the color to use-- actually, both
the foreground and background colors. These are combined into
a single value because that's the way the display controller
wants to see it. Remember, the emphasis here is on speed, not
necessarily convenience! So how do you calculate an attribute
given the foreground and background colors?
Attr% = CalcAttr% (Fore%, Back%)
An Attr% value is never less than 0 or greater than 255-- it
will fit into a single byte. That may be useful to know for
storage purposes. In any event, keep the Attr% in mind,
because we'll be seeing more of it in the future. By the way,
you can unpack an attribute into foreground and background
colors too, if need be:
CALL UnCalcAttr (Attr%, Fore%, Back%)
In addition to XQPrint, there is a routine which allows you to
overlay existing text rather than replacing it. It skips
blank spaces rather than putting them on the display.
CALL XQPrintOver (St$, Row%, Column%, Attr%)
The direct-access routines allow for one, and only one, option
which might slow them down. The IBM CGA and some clone CGAs
flicker horribly if you access their display memory directly.
There is a software fix, but it slows the display down
tremendously. Still, if your program is run on such a CGA,
you'll want to stop the flickering. This may be done so:
CALL AntiSnow (Slow%) ' any nonzero value to stop flicker
You should provide a command-line switch or configuration
option to force anti-flicker support. Don't do it by default,
as the slowdown is very noticeable.
The "menu" approach has become the standard way of allowing a
user to choose between various program options. There are
many ways of designing a menu, but they almost always require
a "highlight" to show the current choice. This highlight is
generally handled by changing the color of the chosen item:
CALL ReColorArea (TopRow%, LftCol%, BotRow%, RtCol%, Attr%)
Text-mode Video page 37
Pop-up windows have become ubiquitous. Naturally, PBWiz
supports them too:
CALL PopWindow (TRow%, LCol%, BRow%, RCol%, Frame%,
Attr%, Grow%, Shade%, TFore%, Title$) ' use one line!
The first four parameters specify the upper left corner and
lower right corner of the window. The window frame, if any,
is created just outside these coordinates. If you choose a
shadow for a 3D effect, that will extend further outside the
window. Keep this in mind if you want to save the part of the
screen under the window... but we'll get to that! Let's see
what options are available for the frame type:
0 no frame
1 single lines
2 double lines
3 single horizontal, double vertical lines
4 double horizontal, single vertical lines
5 block graphic lines
These are the available shadows:
-3 transparent shadow on the right
-2 transparent shadow on the left
-1 solid black shadow on the left
0 no shadow
1+ shadow attribute (use CalcAttr) for a colored shadow
Options for growing windows are as follows:
-1 grow as fast as possible
0 pop onto the screen
1+ grow with a specified delay in milliseconds
(15 works well for me)
The TFore% parameter is the foreground color to use for the
title (Title$), if any (use "" for no title).
It is worth noting that the "milliseconds" value is only
rather approximate. The delay is based on the video card, and
is fairly similar on any computer system, but there will be a
noticeable difference between (say) an XT with a CGA and a 486
with a VGA. Still, it's a useful delay-- if not really
precise, it's at least fairly accurate, and it has a fine
resolution. You can access this delay yourself:
CALL DelayV (MilliSeconds%)
Text-mode Video page 38
I mentioned screen saves a bit earlier. With PBWiz, you can
save any part of a screen, and restore it later to the same
place or an entirely different area. This also offers the
possibility of creating a screen image, storing it in a file,
and reading it into your program, among other things. It
works like this:
Scr$ = ScreenSave$ (TopRow%, LftCol%, BotRow%, RtCol%)
CALL ScreenRestore (Scr$, TopRow%, LftCol%)
It takes about 4K to store a full 80x25 text screen. The
exact calculation is Bytes = Rows * Columns * 2 + 2, if you
care to figure it out yourself. You can also take advantage of
a PBWiz routine to handle it:
Bytes% = CalcSize% (TopRow%, LftCol%, BotRow%, RtCol%) + 2
Finally, we are left with a series of routines which let you
scroll (or clear) any part of the screen:
CALL ScrollDown (TRow%, LCol%, BRow%, RCol%, Times%, Attr%)
CALL ScrollLeft (TRow%, LCol%, BRow%, RCol%, Times%, Attr%)
CALL ScrollRight (TRow%, LCol%, BRow%, RCol%, Times%, Attr%)
CALL ScrollUp (TRow%, LCol%, BRow%, RCol%, Times%, Attr%)
If you attempt to scroll zero times, or more times than there
are rows (or columns, depending on which way you scroll), the
specified area of the screen will be cleared. The Attr% gives
the color to use on the cleared area of the screen.
Credits page 39
I'd like to thank Dave Navarro for letting me in on the world
of PowerBASIC. His assistance has been most valuable to me in
many respects. Without him, this library would have taken much
longer to get off the ground or would perhaps not even exist.
I would also like to thank Spectra, publishers of PowerBASIC,
for sending me the evaluation copy of PowerBASIC which led to
my decision to write this library.